/**
 * @file dev.c
 * @author lcz (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-11-06
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "dev/dev.h"
#include "cpu/irq.h"
#include "tools/kilb.h"
// 驱动程序，后面各种设备的操作就是调用驱动程序
// 各种设备接口的抽象实现  data 这么用就是底层驱动的事了
// os提供的统一抽象接口
// 用主设备号及次设备（唯一标识一个设备）号返回设备id（唯一标识一个设备）

// 将tty.c中定义的tty设备作用域扩展到该文件中
extern dev_desc_t dev_tty_desc;
extern dev_desc_t dev_disk_desc;
#define DEV_TABLE_SIZE 128
static dev_desc_t *dev_desc_tbl[] = {
    &dev_tty_desc,
    &dev_disk_desc,
};

// 特定的某个具体的设备的定义
static device_t dev_tbl[DEV_TABLE_SIZE];

/**
 * @brief 判断设备号是否合理且设备存在，已打开
 * @param dev_id device id
 * @return * int 放回0存在且正常，返回1不存在或不合理
 */
static int is_devid_bad(int dev_id)
{
    if ((dev_id < 0) || (dev_id >= DEV_TABLE_SIZE))
    {
        return 1;
    }

    // devid 没有打开
    if (dev_tbl[dev_id].desc == (dev_desc_t *)0)
    {
        return 1;
    }

    return 0;
}

/**
 * @brief 打开tty设备
 *
 * @param dev 设备指针
 * @return int
 */
int dev_open(int major, int minor, void *data)
{
    irq_state_t state = irq_enter_protection();
    device_t *free_dev = (device_t *)0;
    /**
     * 循环遍历设备，注意已使用的设备号按序增加，对于一个输入的主设备号及次设备
     * 号，再遍历表的时候，要么open_count大于0且主设备号及次设备号对不上，
     * 要么该设备还未打开，我们分配后打开，要么已经打开，我们直接放回设备编号
     */
    for (int i = 0; i < sizeof(dev_tbl) / sizeof(dev_tbl[0]); i++)
    {
        device_t *dev = dev_tbl + i;
        if (dev->open_count == 0)
        {
            free_dev = dev;
            // break;
            // 注意此处不用break，找到了空闲的设备我们也不退出，好比，我们打开的设备是在最后的，
            // 如果break，那么假定我们打开了前三个设备，后来第二个设备关闭了，再后来我们要再次打开第三
            // 个设备时，我们再第二个设备处就找到了空指针，我们就放回了取创建新设备，这是错误的
        }
        else if ((dev->desc->major == major) && (dev->minor == minor))
        {
            // 再次打开
            dev->open_count++;
            irq_leave_protection(state);
            return i;
        }
    }
    /**
     * 遍历我们的设备表，发现没有该设备，此时可以是该设备还没打开，也能使主次设备号输入错误哦，
     * 哦我们遍历主设备号，若主设备号正确，者我们创建该设备，前面for循环已经检查没有相同的主次
     * 设备号，同时相同（与输入参数相同）,所以遍历主设备号后我们便可以确保次设备号再改主设备号
     * 下不重复
     */
    dev_desc_t *desc = (dev_desc_t *)0;
    for (int i = 0; i < sizeof(dev_desc_tbl) / sizeof(dev_desc_tbl[0]); i++)
    {
        dev_desc_t *d = dev_desc_tbl[i];
        if (d->major == major)
        {
            desc = d;
            break;
        }
    }
    // desc 与 free_dev均不为空时分配设备
    if (desc && free_dev)
    {
        free_dev->minor = minor;
        free_dev->data = data;
        free_dev->desc = desc;

        int err = desc->open(free_dev);
        if (err == 0)
        {
            free_dev->open_count = 1;
            irq_leave_protection(state);
            return free_dev - dev_tbl;
            // 放回设备号
        }
    }

    irq_leave_protection(state);
    return -1;
}

/**
 * @brief 设备读 dev read
 *
 * @param dev_id device id
 * @param addr 读取的地址
 * @param bug 缓冲区
 * @param size 读取字节大小
 * @return * int 读取的字节数
 */
int dev_read(int dev_id, int addr, char *buf, int size)
{
    if (is_devid_bad(dev_id))
    {
        return -1;
    }

    device_t *dev = dev_tbl + dev_id;
    return dev->desc->read(dev, addr, buf, size);
}

/**
 * @brief 设备写 dev write
 *
 * @param dev_id device id
 * @param addr 读取的地址
 * @param buf 缓冲区
 * @param size 读取字节大小
 * @return * int 读取的字节数
 */
int dev_write(int dev_id, int addr, char *buf, int size)
{
    if (is_devid_bad(dev_id))
    {
        return -1;
    }

    device_t *dev = dev_tbl + dev_id;
    return dev->desc->write(dev, addr, buf, size);
}

/**
 * @brief 设备控制指令
 *
 * @param dev_id device id
 * @param cmd 控制指令
 * @param arg0 参数0
 * @param arg1 参数1
 * @return * int
 */
int dev_control(int dev_id, int cmd, int arg0, int arg1)
{
    if (is_devid_bad(dev_id))
    {
        return -1;
    }

    device_t *dev = dev_tbl + dev_id;
    return dev->desc->control(dev, cmd, arg0, arg1);
}
/**
 * @brief 关闭设备
 */
void dev_close(int dev_id)
{
    if (is_devid_bad(dev_id))
    {
        return;
    }
    irq_state_t state = irq_enter_protection();
    device_t *dev = dev_tbl + dev_id;
    // 保护一下
    if (--dev->open_count == 0)
    {
        // 设备打开的次数--后为0则关闭
        dev->desc->close(dev);
        kernel_memset(dev, 0, sizeof(device_t));
    }
    irq_leave_protection(state);
    return;
}
